﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System.Reflection;

namespace GameDriver
{
    /// <summary>
    /// A modular and flexible content handler which can be used to load and store all sorts of XNA content file.
    /// </summary>
    public class ContentHandler
    {
        #region PRIVATE VARIABLES
        private ContentManager manager;
        private bool ready;
        //A dictionary of dictionaries - the first is split by type, then by individual file name.
        private Dictionary<Type, Dictionary<string, Object>> data;
        #endregion

        #region PUBLIC VARIABLES AND PROPERTIES
        /// <summary>False if the content handler has been paused, true otherwise.</summary>
        public bool Ready { get { return ready; } }
        #endregion

        #region CONSTRUCTOR AND PUBLIC METHODS
        /// <summary>
        /// Create a new content handler with the given content manager.
        /// </summary>
        /// <param name="Manager">The game's base content manager (needed to base this on).</param>
        public ContentHandler(IServiceProvider Manager)
        {
            //Create a new manager based on the one given.
            manager = new ContentManager(Manager);
            //Set the root directory so it knows where to look for the content files.
            manager.RootDirectory = "Content";

            ready = true;

            data = new Dictionary<Type, Dictionary<string, object>>();
        }

        /// <summary>
        /// Load/retrieve an asset of the given name and type.  Loads the content if it   
        /// hasn't been loaded before, or returns a reference to it otherwise.
        /// </summary>
        /// <typeparam name="T">The type of asset to retrieve (I.E. Texture2D)</typeparam>
        /// <param name="assetName">The name of the asset to load as a string.</param>
        /// <returns>The loaded asset, as the given type.</returns>
        public T Get<T>(string assetName)
        {
            if (!ready) //Can't load or retrieve data if the handler isn't loaded!
                throw new Exception("ERR: Content Handler is currently unloaded!");

            //Get the type of object they want.
            Type t = typeof(T);
            if (!data.ContainsKey(t)) //If we don't have a sub-dictionary for that type
            {
                //Create and add one.
                data.Add(t, new Dictionary<string, object>());
            }
            if (!data[t].ContainsKey(assetName)) //If the data is not already loaded.
                data[t].Add(assetName, manager.Load<T>(assetName)); //Load it and add it to the appropriate dictionary.
            //Look up the content by type (T) then name (assetName), and cast it as the appropriate type (T) before returning.
            return (T)data[t][assetName];
        }

        /// <summary>Unload all the content from this handler, but remember where the content was.</summary>
        public void Unload()
        {
            manager.Unload();
            foreach (Dictionary<string, object> dict in data.Values)
                foreach (string cont in dict.Keys)
                    dict[cont] = null;
            ready = false;
        }

        /// <summary>Reloads all the content from this handler.</summary>
        public void Reload()
        {
            foreach (Type t in data.Keys)
                foreach (string cont in data[t].Keys)
                {
                    //Because generics require compile-time type-safety,
                    //you have to invoke the method via reflection to use
                    //a type determined at run-time.
                    MethodInfo method = GetType().GetMethod("Get")
                                            .MakeGenericMethod(new Type[] { t });
                    method.Invoke(this, new object[] { cont });
                }
            ready = true;
        }
        #endregion
    }
}
